--[[

    Player Jetpack plugin.
    Version 01/03/2019.

    Description:
    A Lua plugin to brings a new feature to the players, a functional jetpack.


        MIT License

        Copyright (c) 2018 Anggara Yama Putra

        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:

        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.

        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.

]]


-- Remove unecessary table(s).
Jetpack.Game = nil;


-- Local variables.
local debug = Jetpack.Debug;
local module = Jetpack.UI;
local txt = Jetpack.UI.Text;
local box = Jetpack.UI.Box;
local syncvalue = Jetpack.SyncValue;
local enum = Jetpack.Enum;
local size = UI.ScreenSize();
local center = { x = size.width/2 , y = size.height/2 };
local lastBarIndex = 0;
local lastFuelPercent = 0;


-- SyncValue init(s).
syncvalue.HasJetpack.Instance = UI.SyncValue.Create( string.format( syncvalue.HasJetpack.NAME , UI.PlayerIndex() ) );
syncvalue.OnOff.Instance      = UI.SyncValue.Create( string.format( syncvalue.OnOff.NAME , UI.PlayerIndex() ) );
syncvalue.Fuel.Instance       = UI.SyncValue.Create( string.format( syncvalue.Fuel.NAME , UI.PlayerIndex() ) );

if syncvalue.HasJetpack.Instance ~= nil
then
    debug:print( string.format( "HasJetpack(%s)", syncvalue.HasJetpack.Instance.name ) );
end
if syncvalue.OnOff.Instance ~= nil
then
    debug:print( string.format( "OnOff(%s)", syncvalue.OnOff.Instance.name ) );
end
if syncvalue.Fuel.Instance ~= nil
then
    debug:print( string.format( "Fuel(%s)", syncvalue.Fuel.Instance.name ) );
end


-- UI element init(s).
txt.Fuel.Percentage.Config.format  = txt.Fuel.Percentage.Config.text;


-- Creates tooltips text.
function txt.Tooltip:Create()
    if self.Instance ~= nil
    then
        debug:print( "JP.tips:Create() not nil." );
        return;
    end

    self.Config.x       = size.width - self.Config.width - 16;
    self.Config.y       = center.y * 2 - 16 - 25 - 16 - 25 - 25 - self.Config.height;

    self.Instance       = UI.Text.Create();
    self.Instance:Hide();
    self.Instance:Set( self.Config );

    debug:print( "JP.tips:Create() Success." );
end
txt.Tooltip:Create();


-- Creates fuel label text.
function txt.Fuel.Label:Create()
    if self.Instance ~= nil
    then
        debug:print( "Fuel.lbl:Create() not nil." );
        return;
    end

    self.Config.x       = center.x - self.Config.width - txt.Fuel.Percentage.Config.width/2 - 16;
    self.Config.y       = center.y * 2 - 32 - 25 - self.Config.height;

    self.Instance       = UI.Text.Create();
    self.Instance:Hide();
    self.Instance:Set( self.Config );

    debug:print( "Fuel.lbl:Create() Success." );
end
txt.Fuel.Label:Create();


-- Creates fuel meter bar.
function box.Fuel:Create()
    if #self.Instances > 0
    then
        debug:print( "Fuel.box:Create() " ..#self.Instances.. "." );
        return;
    end

    local config    = self.Config;
    config.y        = txt.Fuel.Label.Config.y;
    config.x        = txt.Fuel.Label.Config.x + txt.Fuel.Label.Config.width;

    if config.amount < 3
    then
        config.amount = 3;
        debug:print( "Fuel.box:Create() forced to 3." );
    end

    local n    = config.amount;
    local low, medium, high = math.ceil( 0.3 * n ), math.ceil( 0.4 * n ), math.ceil( 0.3 * n );
    local mult = (1/(config.amount+1));

    config.r = self.Config.Low.r;
    config.g = self.Config.Low.g;
    config.b = self.Config.Low.b;
    config.a = self.Config.Low.a;
    for i = 1, n
    do
        if i == low + 1
        then
            config.r = self.Config.Medium.r;
            config.g = self.Config.Medium.g;
            config.b = self.Config.Medium.b;
            config.a = self.Config.Medium.a;
        elseif i == n - high + 1
        then
            config.r = self.Config.High.r;
            config.g = self.Config.High.g;
            config.b = self.Config.High.b;
            config.a = self.Config.High.a;
        end

        config.x = config.x + 4 + self.Config.width;
        local height = config.height * ( ( (i+1) * mult )^2 );
        height = -math.tointeger( math.ceil( height ) );

        self.Instances[i] = UI.Box.Create();
        self.Instances[i]:Hide();
        self.Instances[i]:Set( config );
        self.Instances[i]:Set( {height = height} );
    end

    debug:print( "Fuel.box:Create() Success." );
end
box.Fuel:Create();


-- Sets fuel meter bar with given percentage value.
function box.Fuel:Set( value )
    if #self.Instances <= 0
    then
        debug:print( "Fuel.box:Set() <= 0." );
        return;
    end

    local config    = self.Config;
    local n         = config.amount;
    local percent   = value/100;
    local bars      = percent * n;
    if percent > 0.9
    then
        bars = math.floor( bars );
    else
        bars = math.ceil( bars );
    end

    if bars > n
    then
        bars = n;
    elseif bars < 0
    then
        bars = 0;
    end

    if bars > lastBarIndex
    then
        for i = lastBarIndex, bars
        do
            local box = self.Instances[i];
            if box ~= nil
            then
                box:Show();
            end
        end
    elseif bars < lastBarIndex
    then
        for i = lastBarIndex, bars+1, -1
        do
            local box = self.Instances[i];
            if box ~= nil
            then
                box:Hide();
            end
        end
    else
        return;
    end

    lastBarIndex = bars;

    debug:print( "Fuel.box:Set() Success." );
end


-- Creates fuel meter text.
function txt.Fuel.Percentage:Create()
    if self.Instance ~= nil
    then
        debug:print( "Fuel.%:Create() not nil." );
        return;
    end

    self.Config.x       = box.Fuel.Config.x + 16;
    self.Config.y       = txt.Fuel.Label.Config.y - self.Config.height/2;

    self.Instance       = UI.Text.Create();
    self.Instance:Hide();
    self.Instance:Set( self.Config );

    debug:print( "Fuel.%:Create() Success." );
end
txt.Fuel.Percentage:Create();


-- Sets fuel meter text with given percentage value.
function txt.Fuel.Percentage:Set( value )
    if self.Instance == nil
    then
        debug:print( "Fuel.%:Set() nil." );
        return;
    end

    local config = self.Config;
    config.r   = box.Fuel.Config.High.r
    config.g   = box.Fuel.Config.High.g
    config.b   = box.Fuel.Config.High.b

    local percent = value/100;
    if percent > 0.3 and percent <= 0.7
    then
        config.r   = box.Fuel.Config.Medium.r
        config.g   = box.Fuel.Config.Medium.g
        config.b   = box.Fuel.Config.Medium.b
    elseif percent <= 0.3
    then
        config.r   = box.Fuel.Config.Low.r
        config.g   = box.Fuel.Config.Low.g
        config.b   = box.Fuel.Config.Low.b
    end

    config.text = string.format( config.format , value );
    self.Instance:Set( config );

    debug:print( "Fuel.%:Set() Success." );
end


-- Creates jetpack status label text.
function txt.Status.Label:Create()
    if self.Instance ~= nil
    then
        debug:print( "Status.lbl:Create() not nil." );
        return;
    end

    self.Config.x       = txt.Fuel.Label.Config.x;
    self.Config.y       = txt.Fuel.Label.Config.y - txt.Fuel.Label.Config.height - 16;

    self.Instance       = UI.Text.Create();
    self.Instance:Hide();
    self.Instance:Set( self.Config );

    debug:print( "Status.lbl:Create() Success." );
end
txt.Status.Label:Create();


-- Creates jetpack on/off status text.
function txt.Status.OnOff:Create()
    if self.Instance ~= nil
    then
        debug:print( "Status.onoff:Create() not nil." );
        return;
    end

    self.Config.x       = txt.Status.Label.Config.x + txt.Status.Label.Config.width;
    self.Config.y       = txt.Status.Label.Config.y;

    self.Instance       = UI.Text.Create();
    self.Instance:Hide();
    self.Instance:Set( self.Config );

    debug:print( "Status.onoff:Create() Success." );
end
txt.Status.OnOff:Create();


-- Sets jetpack on/off status text with given boolean value.
function txt.Status.OnOff:Set( value )
    if self.Instance == nil
    then
        debug:print( "Status.onoff:Set() nil." );
        return;
    end

    local config = self.Config;

    if value
    then
        config.text = config.On.text;
        config.r    = config.On.r;
        config.g    = config.On.g;
        config.b    = config.On.b;
    else
        config.text = config.Off.text;
        config.r    = config.Off.r;
        config.g    = config.Off.g;
        config.b    = config.Off.b;
    end

    self.Config.x       = txt.Status.Label.Config.x + txt.Status.Label.Config.width;
    self.Config.y       = txt.Status.Label.Config.y;

    self.Instance:Set( config );

    debug:print( "Status.onoff:Set() Success." );
end


-- Checks whether the player has jetpack.
function module:HasJetpack()
    local var = syncvalue.HasJetpack.Instance;
    return var ~= nil and var.value;
end


-- Checks whether the jetpack is on.
function module:IsOn()
    local var = syncvalue.OnOff.Instance;
    return var ~= nil and var.value;
end


-- Gets the jetpack fuel value.
function module:GetFuel()
    local var = syncvalue.Fuel.Instance;
    return var ~= nil and var.value or -1;
end


-- Checks whether the jetpack fuel is empty.
function module:IsFuelEmpty()
    return self:GetFuel() > 0;
end


-- HasJetpack OnSync() hook.
function syncvalue.HasJetpack:OnSync()
    if self.Instance.value
    then
        txt.Tooltip.Instance:Show();
        txt.Status.Label.Instance:Show();
        txt.Fuel.Label.Instance:Show();
        txt.Status.OnOff.Instance:Show();
        txt.Fuel.Percentage.Instance:Show();
        box.Fuel:Set( lastFuelPercent );
    else
        txt.Tooltip.Instance:Hide();
        txt.Status.Label.Instance:Hide();
        txt.Fuel.Label.Instance:Hide();
        txt.Status.OnOff.Instance:Hide();
        txt.Fuel.Percentage.Instance:Hide();
        box.Fuel:Set( -2 );
    end
end


-- OnOff OnSync() hook.
function syncvalue.OnOff:OnSync()
    txt.Status.OnOff:Set( self.Instance.value );
end


-- Fuel OnSync() hook.
function syncvalue.Fuel:OnSync()
    local value = self.Instance.value;
    lastFuelPercent = ( value == nil and -1 or value/Jetpack.FUEL_MAX * 100 );
    txt.Fuel.Percentage:Set( lastFuelPercent );

    -- Update the bar only when player has jetpack.
    if module:HasJetpack()
    then
        box.Fuel:Set( lastFuelPercent );
    end
end


-- OnInput hook.
function module:OnInput( buttons )
    if buttons[UI.KEY.T]
    then
        UI.Signal( enum.SIGNAL.TOGGLE );
    else
        if buttons[UI.KEY.SHIFT]
        then
            UI.Signal( enum.SIGNAL.LANDING );
        end

        if buttons[UI.KEY.W]
        then
            UI.Signal( enum.SIGNAL.MOVE_FORWARD );
        end

        if buttons[UI.KEY.S]
        then
            UI.Signal( enum.SIGNAL.MOVE_BACKWARD );
        end

        if buttons[UI.KEY.A]
        then
            UI.Signal(enum.SIGNAL.MOVE_LEFT );
        end

        if buttons[UI.KEY.D]
        then
            UI.Signal( enum.SIGNAL.MOVE_RIGHT );
        end

        if buttons[UI.KEY.SPACE]
        then
            UI.Signal( enum.SIGNAL.MOVE_UPWARD );
        end
    end
end


-- Default hook executions.
function syncvalue.HasJetpack.Instance:OnSync()
    syncvalue.HasJetpack:OnSync();
end
function syncvalue.OnOff.Instance:OnSync()
    syncvalue.OnOff:OnSync();
end
function syncvalue.Fuel.Instance:OnSync()
    syncvalue.Fuel:OnSync();
end
function UI.Event:OnInput( buttons )
    module:OnInput( buttons );
end


print( "UI.Jetpack.Main is loaded!" );

